這一篇我想來學習一下Promise。
為什麼說是學習呢?因為我發現我無法跟人解釋這個東西,因此才決定重新學習Promise
以及他後續延伸出來的async/await
但在講之前我們必須先知道,他為什麼被創造出來,他是為了改變什麼事情。
讓我們先看一下演進圖:
由這邊我們可以得知,在Promise
還沒出來前我們都是使用Callback
來處理非同步的事情,但相信大家一定都聽過恐怖的Callback Hell
吧。
由於使用Callback很容易會產生 callback hell的狀況跟難以維護的code。
因此Promise
就誕生了,他的出現讓我們有:
1.更清晰的code結構:通過Promise,讓非同步操作的部分分離出來,降低了code的複雜性。
2.處理錯誤更容易:主要是因為 Promise提供了.catch()
来捕獲和處理非同步操作中的錯誤。
3.避免再出現Callback Hell
:透過.then()將非同步操作連接起來,讓code更加扁平跟可讀。
4.更好的流程控制:我們可以根據Promise的狀態,來決定下一步的操作。
Promise,如字面的意思就代表 承諾。
網路上有大大幫我們整理好了Promis的流程圖:
圖片來源:https://hackmd.io/@wheat0120/javascript-promise
當我們今天拿到一個Promise 的時候,代表這個 Promise 在之後可能會有幾種狀況發生:
成功 (fulfilled)
用 resolve()
來兌現
失敗 (rejected)
用 reject()
來表示失敗
還在執行中 (pending)
一直沒有回傳
上述3個承諾我們統稱為:state
。
Promise除了有 state
這個屬性外,還有一個屬性我們需要知道,那就是**result
** 。
result
:執行完 Promise 後的結果值。
當決定好了狀態(state)後,就會依照我們所設定好的程式開始執行。
Promise 是一個物件建構子 (constructor),使用時需要先從 Promise 物件產生物件實例 (instance),再使用繼承特性的 instance 去包裝程式碼的 callback 流程。
基本的 Primise 宣告方式如下:
// 建立 instance
var promise = new Promise((resolve, reject) => {
// executor code
})
我們可以看到在 Callback 函式內有兩個引數,分別是 resolve 跟 reject 。
這是由 JS 提供、用來決定 Promise 結果狀態時使用的兩個function :
resolve
:
result
為給定的值。.then()
部分來處理成功狀態的Promise。reject
:
.catch()
部分来處理拒絕狀態的Promise。// 流程控制
promise
.then(...step1...)
.then(...step2...)
Promise 是用於進行流程控制的物件 (容器),它具備了 callback 的優點,但透過 .then()
來標明流程,而 .then()
之間可以互相鏈結 (chaining),把之前「一層包一層的 callback」,轉換成 .then()
的串接。
我們來看一段code ,在上面有備註,這樣會更清楚:
var error = false
// 建構 Promise object
var person = new Promise((resolve, reject) => {
setTimeout(() => {
if (error) {
return reject('error happened')
}
resolve({
name: 'Jeff',
age: 35,
level: 999
})
}, 100)
})
// 使用 Promise object
// 接到 resolve就執行.then()系列
// 若接到 reject 就執行 catah()
person
.then((man) => {
console.log('Welcome to 2023', man)
return man.name + 'say: hi, 2023'
})
.then((data) => {
console.log(data)
return person
})
.catch(() => {
console.warn(error)
})
查找資料時看到的案例:
const apiURL = `https://webdev.alphacamp.io/api/lyrics/Coldplay/Yellow.json`
// 阻塞函式
function blockingTest() {
const delay = 2000
const end = Date.now() + delay
console.log('blocking start')
while (Date.now() < end) { }
console.log('blocking end')
}
function promise() {
console.log(1)
new Promise((resolve, reject) => {
console.log(2)
blockingTest()
console.log(3)
fetch(`${apiURL}`)
.then(res => res.json())
.then(res => {
resolve(res)
console.log(4)
})
console.log(5)
}).then(res => {
console.log(6, res)
})
console.log(7)
}
promise()
Answer
// 1
// 2
// blocking start
// blocking end
// 3
// 5
// 7
// 4
// 6, res
你答對了嗎?我承認我答錯了XD
我們一步一步來看。
我們執行了promise這個function ,因此console印出了1。
之後我們新建立了一個Promise,在等待確認state的時候遇到第二個console,因此會印出2。
之後執行了 blockingTest()
這個function,他模擬一個長時間運行的同步操作,阻塞了程式的執行,並且遇到了2個console因此印出 "blocking start" 和 "blocking end"。
接下來遇到了fetch
,會發出一個非同步的請求,因此這段會先丟到event queue 等待,程式會繼續往下執行到console.log(5)
並且印出5。
.then()
由於還不確認state的結果會是fulfilled
還是rejected
,因此不執行,程式會繼續往下執行到console.log(7)
並且印出7。
此時event loop監測到stack已經空了,這時候就會把event queue裡正在等待fetch
拉到stack並執行,因為可以確實連接上api,因此.then()
就會被執行,到第二個.then()
就會確認Promise 的 state 是fulfilled
,並且印出4。
因為Promise 的 state 是fulfilled
,因此就會執行
.then(res => {
console.log(6, res) // 6
})
最後印出6跟res裡的資料。
終於把失去的記憶給找了回來,接下來還有async/await
要找回關於他的記憶。
參考資料:
https://www.cythilya.tw/2018/10/31/promise/
https://hackmd.io/@wheat0120/javascript-promise